<?php

	/**
	 * @see Bike_Js_Abstract
	 */
	require_once 'Bike/Js/Abstract.php';
	
	/**
	 * @see Bike_String
	 */
	require_once 'Bike/String.php';
	
	
	class Bike_Js_Base extends Bike_Js_Abstract
	{
		protected $_settings;

		public function __construct()
		{			$args = func_get_args();
			$this->beforeInit();
			$options = $this->makeOptions($args);
			parent::__construct($options);
		}

		protected function getAvailableSettings()
		{
			return array();
		}

		public function makeOptions(array $Args=null)
		{			$options = array();
			$argsCount = sizeof($Args);
			if($argsCount == 1 && is_array($Args[0]))
			{
				$options = $Args[0];
			}
			return $options;
		}

		public function beforeInit()
		{			
			$this->initDefaultSettings();
		}
		
		protected function initDefaultSettings()
		{
				
		}

		public function __set($Name, $Value)
	    {
	        $methodName = 'set'.ucfirst($Name);
	        $this->$methodName($Value);
	    }

	    public function __get($Name)
	    {
	        $methodName = 'get'.ucfirst($Name);
		    return $this->$methodName();
	    }

		public function __call($MethodName, array $Args=null)
		{			try
			{				return parent::__call($MethodName, $Args);
			}
			catch(Bike_Exception $e)
			{
				if(substr($MethodName, 0, 3) == 'get')
	    		{
	    			$Name = substr($MethodName, 3);
	    			$name = $this->lowerCaseFirst($Name);
	    			$Value = $this->__getSetting($Name);
	    			if(method_exists($this, 'processGet'.$Name))
			        {
			        	$Value = call_user_func(array($this, 'processGet'.$Name), $Value);
			        }
	    			return $Value;
	    		}
	    		elseif(substr($MethodName, 0, 5) == 'unset')
	    		{
	    			$name = substr($MethodName, 5);
	    			return $this->__unsetSetting($name);
	    		}
	    		elseif(substr($MethodName, 0, 5) == 'isset')
	    		{
	    			$name = substr($MethodName, 5);
	    			return $this->__issetSetting($name);
	    		}
	    		elseif(substr($MethodName, 0, 3) == 'set')
	    		{
	    			$Name = substr($MethodName, 3);
	    			$name = $this->lowerCaseFirst($Name);
	    			if(sizeof($Args) <= 1)
	    			{
	    				$Value = reset($Args);
	    			}
	    			else
	    			{	    				$Value = $Args;
	    			}
	    			$Value = $this->_glue($Value, $this->$name);
	    			if(method_exists($this, 'processSet'.$Name))
			        {
			        	$value = call_user_func(array($this, 'processSet'.$Name), $Value);
			        }
			        else
			        {
			        	$value = $Value;
			        }
	    			$this->__setSetting($Name, $value);
	    			return $this;
	    		}
    		}
    		require_once 'Bike/Js/Exception.php';
    		throw new Bike_Js_Exception('Undefined method: '.$MethodName);
		}

		protected function _glue($Value, $OldValue)
		{			if($Value instanceof Bike_Js_Glue)
   			{
   				$methodName = $Value->getMethodName();
   				$args = $Value->getArgs();
   				$val = $OldValue;
   				if(is_object($val) && method_exists($val, $methodName))
   				{
   					$Value = call_user_func_array(array($val, $methodName), $args);
   				}
   				elseif(is_array($val))
   				{
   					switch($methodName)
   					{
   						case '_append':
   									foreach($args as $arg)
   									{
   										array_push($val, $arg);
   									}
   									break;
   						case '_prepend':
   									foreach(array_reverse($args) as $arg)
   									{
   										array_unshift($val, $arg);
   									}
   									break;
   						case '_merge':
   									array_unshift($args, $val);
   									$val = call_user_func_array('array_merge', $args);
   									break;
   						default: 	require_once 'Bike/Js/Exception.php';
   									throw new Bike_Js_Exception('_append and _prepend methods available only for arrays');
   					}
   					$Value = $val;
   				}
   				else
   				{
   					$Value = $args;
   					if(sizeof($Value) == 1)
   					{
   						$Value = reset($args);
   					}
   				}
   			}
   			return $Value;
		}

		protected function lowerCaseFirst($Name)
		{			return Bike_String::factory($Name)->lowerCaseFirst();
		}

		private function __setSetting($Name, $Value)
		{			$name = $this->lowerCaseFirst($Name);
			if(!is_array($this->_settings))
			{				$this->_settings = array();
			}
			$ObjectFlag = False;
			if(is_array($Value) && isset($this->_settings[$name]))
			{				if($this->_settings[$name] instanceof self)
				{
					$this->_settings[$name]->setOptions($Value);
					$ObjectFlag = True;
				}
				elseif($this->_settings[$name] instanceof Bike_Js_Variable)
				{					$this->_settings[$name]->_last()->setOptions($Value);
					$ObjectFlag = True;
				}
			}
			if(!$ObjectFlag)
			{
				$this->_settings[$name]	= $Value;
			}
			return $this;
		}

		private function __getSetting($Name)
		{
			return @$this->_settings[$this->lowerCaseFirst($Name)];
		}

		private function __unsetSetting($Name)
		{
			unset($this->_settings[$this->lowerCaseFirst($Name)]);
			return $this;
		}

		private function __issetSetting($Name)
		{
			return isset($this->_settings[$this->lowerCaseFirst($Name)]);
		}

		public function setSettings(array $Values)
		{
			foreach($Values as $name => $value)
			{				$this->$name = $value;
			}
			return $this;
		}

		public function unsetSettings()
		{
			$this->_settings = array();
			return $this;
		}

		public function appendSettings(array $Settings)
		{
			if(!is_array($this->_settings))
			{				$this->_settings = array();
			}
			foreach($Settings as $setting)
			{				if($setting)
				{
					array_push($this->_settings, $setting);
				}
			}
			return $this;
		}

		public function prependSettings(array $Settings)
		{
			if(!is_array($this->_settings))
			{
				$this->_settings = array();
			}
			foreach(array_reverse($Settings) as $setting)
			{
				if($setting)
				{
					array_unshift($this->_settings, $setting);
				}
			}
			return $this;
		}

		public function getSettings(/* OmitSettings */)
		{
			$args = func_get_args();
			$omitSettings = array_unique(array_merge($this->_makeOmitSettingsArray($args), $this->getOmitSettings()));
			$settings = array();
			foreach((array) $this->_settings as $name => $value)
			{
				$key = $name;
				if(!is_string($name))
				{
					$key = __CLASS__.$name;
				}
				$settings[$key] = $this->$name;
			}
			foreach($omitSettings as $key)
			{
				unset($settings[$key]);
			}
			return $settings;
		}

		protected function _makeOmitSettingsArray($Args)
		{			$omitSettings = $Args;
			if(is_array(@$Args[0]))
			{
				$omitSettings = $Args[0];
			}
			return (array) $omitSettings;
		}

		public function getOmitSettings()
		{			return array();
		}

		protected function _prepare(/* OmitSettings */)
		{			$args = func_get_args();
			return $this->_prepareValue((object) call_user_func_array(array($this, 'getSettings'), $args));
		}
		
		protected function _prepareValues(/* OmitSettings */)
		{
			$args = func_get_args();			
			$settings = call_user_func_array(array($this, 'getSettings'), $args);
			foreach($settings as $name => $value)
			{
				if($value)
				{
					$settings[$name] = $this->_prepareValue($value);
				}
				else
				{
					unset($settings[$name]);
				}
			}			
			return $settings;
		}

		protected function _prepareSettings(/* Settings */)
		{
			$args = func_get_args();
			$settings = $args;
			if(is_array($args[0]))
			{				$settings = $args[0];
			}
			$toPrepare = array();
			foreach($settings as $key)
			{				$toPrepare[$key] = $this->$key;
			}
			return (object) $toPrepare;
		}

		protected function _prepareValue($Value)
		{			$type = gettype($Value);
			$MethodName = '_prepare'.ucfirst($type);
			if(method_exists($this, $MethodName))
			{				$Value = $this->$MethodName($Value);
			}
			return $Value;
		}
		
		protected function _prepareString($Value)
		{
			return $this->jsonEncode($Value);
		}
		
		protected function _prepareNULL($Value)
		{
			return $this->jsonEncode($Value);
		}
		
		protected function _prepareBoolean($Value)
		{
			return $this->jsonEncode($Value);
		}

		protected function _prepareArray($Value)
		{
			foreach($Value as $name => $value)
			{
				$Value[$name] = $this->_prepareValue($value);
			}
			return $this->jsonEncode($Value);
		}
		
		protected function _prepareObject($Value)
		{		
			if(method_exists($Value, '__toString'))
			{
				return (string) $Value;
			}
			
			foreach($Value as $name => $value)
			{
				$Value->$name = $this->_prepareValue($value);
			}
			
			return $this->jsonEncode($Value);
		}

		public function _toString()
		{
			return $this->_jsonEncode();
		}

		protected function _jsonEncode(/* OmitSettings */)
		{			$args = func_get_args();
			
			return $this->jsonEncode(call_user_func_array(array($this, '_prepare'), $args));
		}
	}
